diff --git a/.gitignore b/.gitignore index f66f34c..6cc8670 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ build/ .classpath .settings/ bin/ + +# IntelliJ +.idea/ +out/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d2db51b..0710a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Support for local JAR file ([#20](https://github.com/diffplug/blowdryer/pull/20)) ## [1.1.1] - 2021-02-12 ### Fixed diff --git a/build.gradle b/build.gradle index f888bee..9322ae0 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ plugins { id 'com.diffplug.spotless-changelog' id 'com.gradle.plugin-publish' id 'com.jfrog.bintray' + id "com.palantir.idea-test-fix" version "0.1.0" // Added to run tests successfully in IntelliJ } apply from: 干.file('base/changelog.gradle') diff --git a/src/main/java/com/diffplug/blowdryer/Blowdryer.java b/src/main/java/com/diffplug/blowdryer/Blowdryer.java index 8fa3150..1bdc949 100644 --- a/src/main/java/com/diffplug/blowdryer/Blowdryer.java +++ b/src/main/java/com/diffplug/blowdryer/Blowdryer.java @@ -21,10 +21,13 @@ import com.diffplug.common.hash.Hashing; import com.diffplug.common.io.Files; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Base64; @@ -34,6 +37,8 @@ import java.util.Map; import java.util.Objects; import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -47,6 +52,10 @@ * determined by {@link BlowdryerSetup}. */ public class Blowdryer { + + private static final String FILE_PROTOCOL = "file:///"; + private static final String JAR_FILE_RESOURCE_SEPARATOR = "!/"; + private Blowdryer() {} static { @@ -111,7 +120,7 @@ public static File immutableUrl(String url) { urlToContent.put(url, dataFile); return dataFile; } - } catch (IOException e) { + } catch (IOException | URISyntaxException e) { throw Errors.asRuntime(e); } } @@ -131,7 +140,35 @@ private static Map loadPropertyFile(File file) throws IOExceptio private static final String PROP_URL = "url"; - private static void download(String url, File dst) throws IOException { + private static void download(String url, File dst) throws IOException, URISyntaxException { + if (url.startsWith(FILE_PROTOCOL)) { + downloadLocal(url, dst); + } else { + downloadRemote(url, dst); + } + } + + private static void downloadLocal(String url, File dst) throws IOException, URISyntaxException { + + String[] splitUrl = url.split(JAR_FILE_RESOURCE_SEPARATOR); + if (splitUrl.length != 2) { + throw new IllegalArgumentException("Expected a file URL in the format: file:///path-to-dependency.jar!/path-to-file.ext"); + } + + String jarPath = splitUrl[0]; + String filename = splitUrl[1]; + + URI jarPathUri = new URI(jarPath); + try (ZipFile jar = new ZipFile(new File(jarPathUri))) { + ZipEntry foundEntry = jar.stream() + .filter(s -> s.getName().equals(filename)).findAny() + .orElseThrow(() -> new FileNotFoundException("Could not find '" + filename + "' in '" + jarPath + "'")); + + java.nio.file.Files.copy(jar.getInputStream(foundEntry), dst.toPath()); + } + } + + private static void downloadRemote(String url, File dst) throws IOException { OkHttpClient client = new OkHttpClient.Builder().build(); Request.Builder req = new Request.Builder().url(url); authPlugin.addAuthToken(url, req); @@ -154,7 +191,7 @@ private static void download(String url, File dst) throws IOException { /** Returns either the filename safe URL, or (first40)--(Base64 filenamesafe)(last40). */ static String filenameSafe(String url) { - String allSafeCharacters = url.replaceAll("[^a-zA-Z0-9-+_\\.]", "-"); + String allSafeCharacters = url.replaceAll("[^a-zA-Z0-9-+_.]", "-"); String noDuplicateDash = allSafeCharacters.replaceAll("-+", "-"); if (noDuplicateDash.length() <= MAX_FILE_LENGTH) { return noDuplicateDash; diff --git a/src/main/java/com/diffplug/blowdryer/BlowdryerSetup.java b/src/main/java/com/diffplug/blowdryer/BlowdryerSetup.java index 47f0e0a..53b52ec 100644 --- a/src/main/java/com/diffplug/blowdryer/BlowdryerSetup.java +++ b/src/main/java/com/diffplug/blowdryer/BlowdryerSetup.java @@ -146,6 +146,18 @@ private GitLab setGlobals() { } } + /** + * Uses the provided {@code jarFile} to extract a file resource. + * @param jarFile Absolute path to JAR on the file system. + */ + public void localJar(File jarFile) { + Objects.requireNonNull(jarFile, "jarFile must not be null."); + Blowdryer.setResourcePluginNull(); + + String rootUrl = "file:///" + jarFile.getAbsolutePath() + "!/"; + Blowdryer.setResourcePlugin(resource -> rootUrl + resource); + } + @NotNull private String getFullResourcePath(String resource) { return (repoSubfolder.isEmpty() ? "" : repoSubfolder + "/") + resource; diff --git a/src/test/java/com/diffplug/blowdryer/BlowdryerPluginTest.java b/src/test/java/com/diffplug/blowdryer/BlowdryerPluginTest.java index dd095e2..7326bf2 100644 --- a/src/test/java/com/diffplug/blowdryer/BlowdryerPluginTest.java +++ b/src/test/java/com/diffplug/blowdryer/BlowdryerPluginTest.java @@ -23,38 +23,48 @@ import org.junit.Test; public class BlowdryerPluginTest extends GradleHarness { + + private static final String SETTINGS_GRADLE = "settings.gradle"; + private static final String BUILD_GRADLE = "build.gradle"; + private void settingsGithub(String tag, String... extra) throws IOException { - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { github('diffplug/blowdryer', 'tag', '" + tag + "') }", Arrays.stream(extra).collect(Collectors.joining("\n"))); } private void settingsGitlab(String tag, String... extra) throws IOException { - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { gitlab('diffplug/blowdryer', 'tag', '" + tag + "') }", Arrays.stream(extra).collect(Collectors.joining("\n"))); } private void settingsCustomGitlab(String tag, String... extra) throws IOException { - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { gitlab('diffplug/blowdryer', 'tag', '" + tag + "').customDomainHttps('gitlab.com') }", Arrays.stream(extra).collect(Collectors.joining("\n"))); } private void settingsGitlabRootFolder(String tag, String... extra) throws IOException { - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { repoSubfolder(''); gitlab('diffplug/blowdryer', 'tag', '" + tag + "') }", Arrays.stream(extra).collect(Collectors.joining("\n"))); } + private void settingsLocalJar(String dependency) throws IOException { + write(SETTINGS_GRADLE, + "plugins { id 'com.diffplug.blowdryerSetup' }", + "blowdryerSetup { localJar(file('" + dependency + "')) }"); + } + @Test public void githubTag() throws IOException { settingsGithub("test/2/a"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'a'", "assert 干.prop('sample', 'name') == 'test'", @@ -62,7 +72,7 @@ public void githubTag() throws IOException { gradleRunner().build(); settingsGithub("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'b'", "assert 干.prop('sample', 'name') == 'testB'", @@ -71,7 +81,7 @@ public void githubTag() throws IOException { // double-check that failures do fail settingsGithub("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "plugins { id 'com.diffplug.blowdryer' }", "assert Blowdryer.file('sample').text == 'a'"); gradleRunner().buildAndFail(); @@ -80,7 +90,7 @@ public void githubTag() throws IOException { @Test public void gitlabTag() throws IOException { settingsGitlab("test/2/a"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'a'", "assert 干.prop('sample', 'name') == 'test'", @@ -88,7 +98,7 @@ public void gitlabTag() throws IOException { gradleRunner().build(); settingsGitlab("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'b'", "assert 干.prop('sample', 'name') == 'testB'", @@ -97,7 +107,7 @@ public void gitlabTag() throws IOException { // double-check that failures do fail settingsGitlab("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "plugins { id 'com.diffplug.blowdryer' }", "assert Blowdryer.file('sample').text == 'a'"); gradleRunner().buildAndFail(); @@ -106,7 +116,7 @@ public void gitlabTag() throws IOException { @Test public void customGitlabTag() throws IOException { settingsCustomGitlab("test/2/a"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'a'", "assert 干.prop('sample', 'name') == 'test'", @@ -114,7 +124,7 @@ public void customGitlabTag() throws IOException { gradleRunner().build(); settingsCustomGitlab("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'b'", "assert 干.prop('sample', 'name') == 'testB'", @@ -123,7 +133,7 @@ public void customGitlabTag() throws IOException { // double-check that failures do fail settingsCustomGitlab("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "plugins { id 'com.diffplug.blowdryer' }", "assert Blowdryer.file('sample').text == 'a'"); gradleRunner().buildAndFail(); @@ -132,7 +142,7 @@ public void customGitlabTag() throws IOException { @Test public void rootfolderGitlabTag() throws IOException { settingsGitlabRootFolder("test/2/a"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('src/main/resources/sample').text == 'a'", "assert 干.prop('src/main/resources/sample', 'name') == 'test'", @@ -140,7 +150,7 @@ public void rootfolderGitlabTag() throws IOException { gradleRunner().build(); settingsGitlabRootFolder("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('src/main/resources/sample').text == 'b'", "assert 干.prop('src/main/resources/sample', 'name') == 'testB'", @@ -149,7 +159,7 @@ public void rootfolderGitlabTag() throws IOException { // double-check that failures do fail settingsGitlabRootFolder("test/2/b"); - write("build.gradle", + write(BUILD_GRADLE, "plugins { id 'com.diffplug.blowdryer' }", "assert Blowdryer.file('src/main/resources/sample').text == 'a'"); gradleRunner().buildAndFail(); @@ -161,10 +171,10 @@ public void devLocal() throws IOException { write("../blowdryer-script/src/main/resources/sample.properties", "name=test", "group=com.diffplug.gradle"); - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { devLocal('../blowdryer-script') }"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", // .replace('\\r', '') fixes test on windows "assert 干.file('sample').text.replace('\\r', '') == 'c\\n'", @@ -177,7 +187,7 @@ public void devLocal() throws IOException { public void multiproject() throws IOException { settingsGithub("test/2/a", "include 'subproject'"); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "assert 干.file('sample').text == 'a'", "assert 干.prop('sample', 'name') == 'test'", @@ -199,7 +209,7 @@ public void multiproject() throws IOException { @Test public void missingResourceThrowsError() throws IOException { settingsGithub("test/2/a"); - write("build.gradle", + write(BUILD_GRADLE, "plugins { id 'com.diffplug.blowdryer' }", "干.file('notPresent')"); Assertions.assertThat(gradleRunner().buildAndFail().getOutput().replace("\r\n", "\n")).contains( @@ -213,7 +223,7 @@ public void cfgTestGroovy() throws IOException { write("../blowdryer-script/src/main/resources/sample.properties", "name=test", "group=com.diffplug.gradle"); - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { devLocal('../blowdryer-script') }"); write("../blowdryer-script/src/main/resources/script.gradle", @@ -223,7 +233,7 @@ public void cfgTestGroovy() throws IOException { "println 干.proj(File.class, 'keyFile', 'location of the keyFile')", "println 干.prop('sample', 'group')", ""); - write("build.gradle", + write(BUILD_GRADLE, "apply plugin: 'com.diffplug.blowdryer'", "ext.pluginPass = 'supersecret'", "ext.keyFile = new File('keyFile.txt')", @@ -267,7 +277,7 @@ public void cfgTestKotlin() throws IOException { @Test public void settingsTest() throws IOException { - write("settings.gradle", + write(SETTINGS_GRADLE, "plugins { id 'com.diffplug.blowdryerSetup' }", "blowdryerSetup { github('diffplug/blowdryer', 'tag', 'test/2/a') }", "import com.diffplug.blowdryer.干", @@ -277,4 +287,28 @@ public void settingsTest() throws IOException { "println 'test was success'"); Assertions.assertThat(gradleRunner().build().getOutput().replace("\r\n", "\n")); } + + @Test + public void localJarFileDownloadExists() throws IOException { + String jarFile = BlowdryerPluginTest.class.getResource("test.jar").getFile(); + settingsLocalJar(jarFile); + + write(BUILD_GRADLE, + "apply plugin: 'com.diffplug.blowdryer'", + "assert 干.file('sample').exists()"); + + gradleRunner().build(); + } + + @Test + public void localJarFileDownloadDoesNotExist() throws IOException { + String jarFile = BlowdryerPluginTest.class.getResource("test.jar").getFile(); + settingsLocalJar(jarFile); + + write(BUILD_GRADLE, + "apply plugin: 'com.diffplug.blowdryer'", + "assert 干.file('invalid-file.txt').exists()"); + + gradleRunner().buildAndFail(); + } } diff --git a/src/test/java/com/diffplug/blowdryer/BlowdryerTest.java b/src/test/java/com/diffplug/blowdryer/BlowdryerTest.java index 14109a6..3d126d6 100644 --- a/src/test/java/com/diffplug/blowdryer/BlowdryerTest.java +++ b/src/test/java/com/diffplug/blowdryer/BlowdryerTest.java @@ -20,6 +20,9 @@ import org.junit.Test; public class BlowdryerTest { + private static final String JAR_FILE_RESOURCE_SEPARATOR = "!/"; + private static final String FILE_PROTOCOL = "file:///"; + @Test public void filenameSafe() { filenameSafe("http://shortName.com/a+b-0-9~Z", "http-shortName.com-a+b-0-9-Z"); @@ -38,4 +41,10 @@ public void cachedFileDeleted_issue_11() { Blowdryer.immutableUrl(test).delete(); Assertions.assertThat(Blowdryer.immutableUrl(test)).hasContent("b"); } + + @Test + public void immutableUrlOfLocalJar() { + String jarFile = BlowdryerPluginTest.class.getResource("test.jar").getFile(); + Assertions.assertThat(Blowdryer.immutableUrl(FILE_PROTOCOL + jarFile + JAR_FILE_RESOURCE_SEPARATOR + "sample")).exists(); + } } diff --git a/src/test/resources/com/diffplug/blowdryer/test.jar b/src/test/resources/com/diffplug/blowdryer/test.jar new file mode 100644 index 0000000..0777c4f Binary files /dev/null and b/src/test/resources/com/diffplug/blowdryer/test.jar differ