From 40660c1ceaff2f395352131635c9b3943ed24a74 Mon Sep 17 00:00:00 2001 From: Hunter Mellema <124718352+hpmellema@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:16:59 -0600 Subject: [PATCH] Cache template directory in init command (#1896) Adds caching of smithy init template directory in the default system temp directory. This greatly speeds up subsequent executions of both the list and create template subcommands of the smithy init. --- .github/workflows/ci.yml | 4 + .../amazon/smithy/cli/CleanCommandTest.java | 59 ++++++- .../amazon/smithy/cli/InitCommandTest.java | 53 ++++++ .../amazon/smithy/cli/IntegUtils.java | 8 +- .../smithy/cli/commands/CleanCommand.java | 49 +++++- .../amazon/smithy/cli/commands/CliCache.java | 45 +++++ .../smithy/cli/commands/InitCommand.java | 158 +++++++++++++----- .../software/amazon/smithy/utils/IoUtils.java | 3 + 8 files changed, 330 insertions(+), 49 deletions(-) create mode 100644 smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CliCache.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d70c06398c1..4ae8f88b8dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,10 @@ jobs: - name: Clean, build and javadoc run: ./gradlew clean build javadoc -Plog-tests --stacktrace + - name: Allow long file names in git for windows + if: matrix.os == 'windows-latest' + run: git config --system core.longpaths true + - name: CLI integration tests if: matrix.java >= 17 run: ./gradlew :smithy-cli:integ -Plog-tests --stacktrace diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/CleanCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/CleanCommandTest.java index 24c7525b6a5..29fb1da6e62 100644 --- a/smithy-cli/src/it/java/software/amazon/smithy/cli/CleanCommandTest.java +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/CleanCommandTest.java @@ -4,18 +4,25 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasLength; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; + import org.junit.jupiter.api.Test; +import software.amazon.smithy.utils.IoUtils; import software.amazon.smithy.utils.ListUtils; + public class CleanCommandTest { + private static final String PROJECT_NAME = "simple-config-sources"; @Test public void exitNormallyIfBuildDirMissing() { - IntegUtils.run("simple-config-sources", ListUtils.of("clean"), result -> { + IntegUtils.run(PROJECT_NAME, ListUtils.of("clean"), result -> { assertThat(result.getExitCode(), equalTo(0)); assertThat(result.getOutput(), hasLength(0)); }); @@ -23,7 +30,7 @@ public void exitNormallyIfBuildDirMissing() { @Test public void deletesContentsOfBuildDir() { - IntegUtils.withProject("simple-config-sources", root -> { + IntegUtils.withProject(PROJECT_NAME, root -> { try { Path created = Files.createDirectories(root.resolve("build").resolve("smithy").resolve("foo")); assertThat(Files.exists(created), is(true)); @@ -35,4 +42,52 @@ public void deletesContentsOfBuildDir() { } }); } + + @Test + public void cleanRemovesAllCacheDirectories() throws IOException { + IntegUtils.clearCacheDirIfExists(); + try { + Files.createDirectories(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH); + assertTrue(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH) + && Files.isDirectory(IntegUtils.SMITHY_ROOT_CACHE_PATH)); + IntegUtils.run(PROJECT_NAME, ListUtils.of("clean"), result -> { + assertThat(result.getExitCode(), equalTo(0)); + assertThat(result.getOutput(), hasLength(0)); + assertFalse(Files.exists(IntegUtils.SMITHY_ROOT_CACHE_PATH)); + }); + } finally { + IntegUtils.clearCacheDirIfExists(); + } + } + + @Test + public void cleanWithTemplateOptionRemovesOnlyTemplateDir() throws IOException { + IntegUtils.clearCacheDirIfExists(); + try { + Files.createDirectories(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH); + assertTrue(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH) + && Files.isDirectory(IntegUtils.SMITHY_ROOT_CACHE_PATH)); + + IntegUtils.withProject(PROJECT_NAME, root -> { + Path created = null; + try { + created = Files.createDirectories(root.resolve("build").resolve("smithy").resolve("foo")); + assertThat(Files.exists(created), is(true)); + RunResult result = IntegUtils.run(root, ListUtils.of("clean", "--templates")); + assertThat(Files.exists(created), is(true)); + assertThat(result.getExitCode(), is(0)); + assertTrue(Files.exists(IntegUtils.SMITHY_ROOT_CACHE_PATH)); + assertFalse(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } finally { + if (created != null) { + IoUtils.rmdir(created); + } + } + }); + } finally { + IntegUtils.clearCacheDirIfExists(); + } + } } diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java index a8f77ba9d19..102a97993af 100644 --- a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java @@ -3,7 +3,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.Test; import software.amazon.smithy.utils.IoUtils; import software.amazon.smithy.utils.ListUtils; @@ -295,6 +300,54 @@ public void badIncludePathFailureExpected() { }); } + @Test + public void cacheCreatedOnFirstCreationOfTemplate() { + IntegUtils.clearCacheDirIfExists(); + IntegUtils.withProject(PROJECT_NAME, root -> { + try { + RunResult resultFirst = IntegUtils.run(root, ListUtils.of("init", "-o", "hello-world")); + assertTrue(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH) + && Files.isDirectory(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH)); + assertThat(resultFirst.getExitCode(), equalTo(0)); + assertThat(resultFirst.getOutput(), + containsString("template repo cloned")); + assertThat(resultFirst.getOutput(), + containsString("Smithy project created in directory: hello-world")); + IoUtils.rmdir(root.resolve("hello-world")); + + RunResult resultSecond = IntegUtils.run(root, ListUtils.of("init", "-o", "hello-world")); + assertTrue(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH) + && Files.isDirectory(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH)); + assertThat(resultSecond.getExitCode(), equalTo(0)); + assertThat(resultSecond.getOutput(), not(containsString("template repo cloned"))); + assertThat(resultSecond.getOutput(), + containsString("Smithy project created in directory: hello-world")); + } finally { + IntegUtils.clearCacheDirIfExists(); + } + }); + } + + @Test + public void noCacheCreatedWhenLocalRepo() { + IntegUtils.clearCacheDirIfExists(); + IntegUtils.withProject(PROJECT_NAME, templatesDir -> { + setupTemplatesDirectory(templatesDir); + + IntegUtils.withTempDir("exitZero", dir -> { + RunResult result = IntegUtils.run( + dir, ListUtils.of("init", "-t", "quickstart-cli", "-u", templatesDir.toString())); + assertThat(result.getOutput(), + containsString("Smithy project created in directory: quickstart-cli")); + assertThat(result.getExitCode(), is(0)); + assertThat(Files.exists(Paths.get(dir.toString(), "quickstart-cli")), is(true)); + + assertFalse(Files.exists(IntegUtils.SMITHY_TEMPLATE_CACHE_PATH)); + assertFalse(Files.exists(IntegUtils.SMITHY_ROOT_CACHE_PATH)); + }); + }); + } + private static void run(List args, Path root) { StringBuilder output = new StringBuilder(); int result = IoUtils.runCommand(args, root, output, Collections.emptyMap()); diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java index 22015c527c0..636a2609d65 100644 --- a/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java @@ -39,7 +39,9 @@ import software.amazon.smithy.utils.MapUtils; public final class IntegUtils { - + public static final Path SMITHY_ROOT_CACHE_PATH = Paths.get(System.getProperty("java.io.tmpdir")) + .resolve("smithy-cache"); + public static final Path SMITHY_TEMPLATE_CACHE_PATH = SMITHY_ROOT_CACHE_PATH.resolve("templates"); private static final Logger LOGGER = Logger.getLogger(IntegUtils.class.getName()); private IntegUtils() {} @@ -146,4 +148,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } }); } + + public static void clearCacheDirIfExists() { + IoUtils.rmdir(SMITHY_ROOT_CACHE_PATH); + } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CleanCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CleanCommand.java index 8873302bdf6..d861e945945 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CleanCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CleanCommand.java @@ -20,8 +20,10 @@ import java.util.logging.Logger; import software.amazon.smithy.build.SmithyBuild; import software.amazon.smithy.build.model.SmithyBuildConfig; +import software.amazon.smithy.cli.ArgumentReceiver; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.Command; +import software.amazon.smithy.cli.HelpPrinter; import software.amazon.smithy.utils.IoUtils; final class CleanCommand implements Command { @@ -40,20 +42,32 @@ public String getName() { @Override public String getSummary() { - return "Removes Smithy build artifacts."; + return "Removes Smithy build artifacts and caches."; } @Override public int execute(Arguments arguments, Env env) { arguments.addReceiver(new ConfigOptions()); + arguments.addReceiver(new Options()); CommandAction action = HelpActionWrapper.fromCommand(this, parentCommandName, this::run); return action.apply(arguments, env); } private int run(Arguments arguments, Env env) { - ConfigOptions options = arguments.getReceiver(ConfigOptions.class); - SmithyBuildConfig config = options.createSmithyBuildConfig(); + ConfigOptions configOptions = arguments.getReceiver(ConfigOptions.class); + Options options = arguments.getReceiver(Options.class); + + if (options.cleanTemplateCache) { + LOGGER.fine(() -> "Clearing template cache."); + if (CliCache.getTemplateCache().clear()) { + LOGGER.fine(() -> "No template cache found."); + } + return 0; + } + + + SmithyBuildConfig config = configOptions.createSmithyBuildConfig(); Path dir = config.getOutputDirectory() .map(Paths::get) .orElseGet(SmithyBuild::getDefaultOutputDirectory); @@ -62,6 +76,35 @@ private int run(Arguments arguments, Env env) { LOGGER.fine(() -> "Directory does not exist: " + dir); } LOGGER.fine(() -> "Deleted directory " + dir); + + LOGGER.fine(() -> "Clearing all caches."); + if (!CliCache.clearAll()) { + LOGGER.fine(() -> "No caches found."); + } + return 0; } + + + private static final class Options implements ArgumentReceiver { + private Boolean cleanTemplateCache = false; + + @Override + public boolean testOption(String name) { + switch (name) { + case "--templates": + case "-t": + cleanTemplateCache = true; + return true; + default: + return false; + } + } + + @Override + public void registerHelp(HelpPrinter printer) { + printer.param("--templates", "-t", null, + "Clean only the templates cache."); + } + } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CliCache.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CliCache.java new file mode 100644 index 00000000000..7d588d1d24b --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CliCache.java @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.cli.commands; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.utils.IoUtils; + +interface CliCache { + Path DEFAULT_TEMP_DIR = Paths.get(System.getProperty("java.io.tmpdir")); + Path ROOT_CACHE_DIR = DEFAULT_TEMP_DIR.resolve("smithy-cache"); + + static CliCache getTemplateCache() { + return () -> ROOT_CACHE_DIR.resolve("templates"); + } + + Path getPath(); + + default boolean clear() { + return IoUtils.rmdir(getPath()); + } + + default Path get() { + Path cachePath = getPath(); + if (Files.exists(cachePath)) { + return cachePath; + } + // If cache dir does not exist, create it and all required parent directories + try { + return Files.createDirectories(cachePath); + } catch (IOException e) { + throw new CliError("Could not create cache at path: " + cachePath); + } + } + + static boolean clearAll() { + return IoUtils.rmdir(ROOT_CACHE_DIR); + } +} diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java index 79f5961897b..3200734f47a 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java @@ -16,8 +16,8 @@ package software.amazon.smithy.cli.commands; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -43,6 +43,7 @@ import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.StringUtils; + final class InitCommand implements Command { private static final int LINE_LENGTH = 100; private static final String COLUMN_SEPARATOR = " "; @@ -89,32 +90,99 @@ private int run(Arguments arguments, Env env) { Options options = arguments.getReceiver(Options.class); StandardOptions standardOptions = arguments.getReceiver(StandardOptions.class); - try { - Path root = Paths.get("."); - Path temp = Files.createTempDirectory("temp"); + boolean isLocalRepo = isLocalRepo(options.repositoryUrl); + Path templateRepoDirPath = getTemplateRepoDirPath(options.repositoryUrl, isLocalRepo); + + // If the cache directory does not exist, create it + if (!Files.exists(templateRepoDirPath)) { + try (ProgressTracker t = new ProgressTracker(env, + ProgressStyle.dots("cloning template repo", "template repo cloned"), + standardOptions.quiet() + )) { + Path templateCachePath = CliCache.getTemplateCache().get(); + String relativeTemplateDir = templateCachePath.relativize(templateRepoDirPath).toString(); + // Only clone the latest commit from HEAD. Do not include history + exec(ListUtils.of("git", "clone", "--depth", "1", "--single-branch", + options.repositoryUrl, relativeTemplateDir), templateCachePath); + } + } - loadSmithyTemplateJsonFile(options.repositoryUrl, root, temp); + validateTemplateDir(templateRepoDirPath, options.repositoryUrl); + + // Check for updates and update repo to latest if applicable + if (!isLocalRepo) { + // update remote + exec(ListUtils.of("git", "fetch", "--depth", "1"), templateRepoDirPath); + String response = exec(ListUtils.of("git", "rev-list", "origin..HEAD"), templateRepoDirPath); + // If a change was detected, force the template repo to update + if (!StringUtils.isEmpty(response)) { + try (ProgressTracker t = new ProgressTracker(env, + ProgressStyle.dots("updating template cache", "template repo updated"), + standardOptions.quiet() + )) { + exec(ListUtils.of("git", "reset", "--hard", "origin/main"), templateRepoDirPath); + exec(ListUtils.of("git", "clean", "-dfx"), templateRepoDirPath); + } + } + } - ObjectNode smithyTemplatesNode = getSmithyTemplatesNode(temp); - if (options.listTemplates) { - this.listTemplates(smithyTemplatesNode, env); - } else { - this.cloneTemplate(temp, smithyTemplatesNode, options, standardOptions, env); - } - } catch (IOException | InterruptedException | URISyntaxException e) { - throw new RuntimeException(e); + ObjectNode smithyTemplatesNode = readJsonFileAsNode(templateRepoDirPath.resolve(SMITHY_TEMPLATE_JSON)) + .expectObjectNode(); + if (options.listTemplates) { + this.listTemplates(smithyTemplatesNode, env); + } else { + this.cloneTemplate(templateRepoDirPath, smithyTemplatesNode, options, standardOptions, env); } return 0; } - private void listTemplates(ObjectNode smithyTemplatesNode, Env env) throws IOException { + private void validateTemplateDir(Path templateRepoDirPath, String templateUrl) { + if (!Files.isDirectory(templateRepoDirPath)) { + throw new CliError("Template repository " + templateRepoDirPath + " is not a directory"); + } + Path templateJsonPath = templateRepoDirPath.resolve(SMITHY_TEMPLATE_JSON); + if (!Files.exists(templateJsonPath) && Files.isRegularFile(templateJsonPath)) { + throw new CliError("Template repository " + templateUrl + + " does not contain a valid `smithy-templates.json`."); + } + } + + private Path getTemplateRepoDirPath(String repoPath, boolean isLocalRepo) { + if (isLocalRepo) { + // Just use the local path if the git repo is local + return Paths.get(repoPath); + } else { + return CliCache.getTemplateCache().get().resolve(getCacheDirFromURL(repoPath)); + } + } + + // Remove any trailing .git + // Remove "/" and ".." and ":" characters so a directory can be created with no nesting + private String getCacheDirFromURL(final String repositoryUrl) { + return repositoryUrl.replace(".git", "") + .replace(":", "_") + .replace("/", "_") + .replace(".", "_"); + } + + private void listTemplates(ObjectNode smithyTemplatesNode, Env env) { try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stdout())) { buffer.println(getTemplateList(smithyTemplatesNode, env)); } } + private boolean isLocalRepo(String repoPath) { + try { + Path localPath = Paths.get(repoPath); + return Files.exists(localPath); + } catch (InvalidPathException exc) { + return false; + } + + } + private String getTemplateList(ObjectNode smithyTemplatesNode, Env env) { int maxTemplateLength = 0; Map templates = new TreeMap<>(); @@ -158,6 +226,7 @@ private String getTemplateList(ObjectNode smithyTemplatesNode, Env env) { return builder.toString(); } + private static void writeTemplateBorder(ColorBuffer writer, int maxNameLength, int maxDocLength) { writer.print(pad("", maxNameLength).replace(" ", "─"), ColorTheme.TEMPLATE_LIST_BORDER) .print(COLUMN_SEPARATOR) @@ -169,9 +238,8 @@ private static String wrapDocumentation(String doc, int maxLength, int offset) { return StringUtils.wrap(doc, maxLength, System.lineSeparator() + pad("", offset), false); } - private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options options, - StandardOptions standardOptions, Env env) - throws IOException, InterruptedException, URISyntaxException { + private void cloneTemplate(Path templateRepoDirPath, ObjectNode smithyTemplatesNode, Options options, + StandardOptions standardOptions, Env env) { String template = options.template; String directory = options.directory; @@ -184,7 +252,7 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options op if (directory == null) { directory = template; } - final Path dest = Paths.get(directory); + Path dest = Paths.get(directory); if (Files.exists(dest)) { throw new CliError("Output directory `" + directory + "` already exists."); } @@ -195,32 +263,27 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options op "Invalid template `%s`. `%s` provides the following templates:%n%n%s", template, getTemplatesName(smithyTemplatesNode), getTemplateList(smithyTemplatesNode, env))); } - ObjectNode templateNode = templatesNode.expectObjectMember(template).expectObjectNode(); - final String templatePath = getTemplatePath(templateNode, template); + String templatePath = getTemplatePath(templateNode, template); List includedFiles = getIncludedFiles(templateNode); - try (ProgressTracker ignored = new ProgressTracker(env, - ProgressStyle.dots("cloning template", "template cloned"), - standardOptions.quiet() - )) { - // Specify the subdirectory to download - exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", templatePath), temp); - // add any additional files that should be included - for (String includedFile : includedFiles) { - exec(ListUtils.of("git", "sparse-checkout", "add", "--no-cone", includedFile), temp); - } - exec(ListUtils.of("git", "checkout"), temp); + Path stagingPath = createStagingRepo(templateRepoDirPath); + + // Specify the subdirectory to check out + exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", templatePath), stagingPath); + // add any additional files that should be included + for (String includedFile : includedFiles) { + exec(ListUtils.of("git", "sparse-checkout", "add", "--no-cone", includedFile), stagingPath); } + exec(ListUtils.of("git", "checkout"), stagingPath); - if (!Files.exists(temp.resolve(templatePath))) { + if (!Files.exists(stagingPath.resolve(templatePath))) { throw new CliError(String.format("Template path `%s` for template \"%s\" is invalid.", templatePath, template)); } - - IoUtils.copyDir(Paths.get(temp.toString(), templatePath), dest); - copyIncludedFiles(temp.toString(), dest.toString(), includedFiles, template); + IoUtils.copyDir(Paths.get(stagingPath.toString(), templatePath), dest); + copyIncludedFiles(stagingPath.toString(), dest.toString(), includedFiles, template, env); if (!standardOptions.quiet()) { try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stdout())) { @@ -229,13 +292,17 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options op } } - private static void loadSmithyTemplateJsonFile(String repositoryUrl, Path root, Path temp) { - exec(ListUtils.of("git", "clone", "--filter=blob:none", "--no-checkout", "--depth", "1", "--sparse", - repositoryUrl, temp.toString()), root); - - exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", SMITHY_TEMPLATE_JSON), temp); + private static Path createStagingRepo(Path repoPath) { + Path temp; + try { + temp = Files.createTempDirectory("temp"); + } catch (IOException exc) { + throw new CliError("Unable to create staging directory for template."); + } + exec(ListUtils.of("git", "clone", "--no-checkout", "--depth", "1", "--sparse", + "file://" + repoPath.toString(), temp.toString()), Paths.get(".")); - exec(ListUtils.of("git", "checkout"), temp); + return temp; } private static ObjectNode getSmithyTemplatesNode(Path jsonFilePath) { @@ -271,7 +338,7 @@ private static List getIncludedFiles(ObjectNode templateNode) { } private static void copyIncludedFiles(String temp, String dest, List includedFiles, - String templateName) throws IOException { + String templateName, Env env) { for (String included : includedFiles) { Path includedPath = Paths.get(temp, included); if (!Files.exists(includedPath)) { @@ -284,7 +351,12 @@ private static void copyIncludedFiles(String temp, String dest, List inc if (Files.isDirectory(includedPath)) { IoUtils.copyDir(includedPath, target); } else if (Files.isRegularFile(includedPath)) { - Files.copy(includedPath, target); + try { + Files.copy(includedPath, target); + } catch (IOException e) { + throw new CliError("Unable to copy included file: " + includedPath + + "to destination directory: " + target); + } } } } diff --git a/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java b/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java index 0439e23f042..1a5107cf421 100644 --- a/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java +++ b/smithy-utils/src/main/java/software/amazon/smithy/utils/IoUtils.java @@ -334,6 +334,9 @@ public static boolean rmdir(Path dir) { Files.walkFileTree(dir, new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // Workaround for Windows systems that set some git packfiles to readonly + file.toFile().setWritable(true); + Files.delete(file); return FileVisitResult.CONTINUE; }