package aQute.bnd.enroute.commands; import java.io.File; import java.io.InputStream; import java.util.Formatter; import java.util.List; import java.util.Map.Entry; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import aQute.bnd.build.Workspace; import aQute.bnd.main.bnd; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.Resource; import aQute.bnd.osgi.Verifier; import aQute.lib.getopt.Arguments; import aQute.lib.getopt.Description; import aQute.lib.getopt.Options; import aQute.lib.io.IO; @Description("OSGi enRoute Commands") public class EnrouteCommand { private final static Logger logger = LoggerFactory.getLogger(EnrouteCommand.class); private bnd bnd; private EnrouteOptions opts; public EnrouteCommand(bnd bnd, EnrouteOptions opts) throws Exception { this.bnd = bnd; this.opts = opts; List<String> args = opts._arguments(); if (args.size() == 0) { // Default command printHelp(); } else { // Other commands String cmd = args.remove(0); String help = opts._command().execute(this, cmd, args); if (help != null) { bnd.out.print(help); } } } private void printHelp() throws Exception { Formatter f = new Formatter(); opts._command().help(f, this); bnd.out.println(f); f.close(); } @Description("Create a workspace in the base directory (working directory or set with bnd -b <base>). The name of the workspace should be a " + "Bundle Symbolic Name type (like com.example.whatever). If another type of name is " + "necessary you can override it with --anyname. Two directories will be created. One for the bnd" + "workspace and the other for the eclipse workspace. Having two directories makes life a lot" + "easier when you use git, it allows you to clear the bnd workspace with 'git clean -fdx' without" + "killing any personal Eclipse data. It also prevents you from accidentally storing this" + "personal data in git. If you know better, use --single to let these workspaces overlap.\n" + "The directory for the workspaces must be empty unless you specify --update or --force.%n" + "This template will also install a gradle build system and a travis continuous integration" + "control file.%n" + "A workspace cannot be created at the root of a file system. The general layout of the" + "file system is%n" + "%n%n" // + " ../wss/%n" // + " com.acme.prime/%n" + " .metadata/%n" // + " ....%n" + " scm/%n"// + " cnf/%n" // + " build.bnd%n" + " ....%n" + " com.acme.prime.runner.api%n" // ) @Arguments(arg = "workspace") public interface WorkspaceOptions extends Options { @Description("Create a single workspace for the Eclipse workspace and the bnd workspace. This is not recommended because if you fully clean the directory you delete Eclipse metadata.") boolean single(); @Description("In general a workspace should follow the rules for Bundle Symbolic%n" + "Names. If this option is set any name (including one with file seperators) is fine.") boolean anyname(); @Description("Existing files are updated when they are older than the ones in the template") boolean update(); @Description("Update a workspace overwrite all existing files.") boolean force(); } public void _workspace(WorkspaceOptions opts) throws Exception { File base = bnd.getBase(); String name = opts._arguments().get(0); File workspaceDir = Processor.getFile(base, name); name = workspaceDir.getName(); base = workspaceDir.getParentFile(); if (base == null) { bnd.error("You cannot create a workspace in the root (%s). The parent of a workspace %n" + "must be a valid directory. Recommended is to dedicate a directory to %n" + "all (or related) workspaces.", workspaceDir); return; } if (!opts.anyname() && !Verifier.isBsn(name)) { bnd.error("You specified a workspace name that does not follow the recommended pattern " + "(it should be like a Bundle Symbolic name). It is a bit pedantic but it " + "really helps hwne you get many workspaces. If you insist on this name, use the -a/--anyname option."); return; } Workspace ws = bnd.getWorkspace((File) null); if (ws != null && ws.isValid()) { bnd.error( "You are currently in a workspace already (%s) in %s. You can only create a new workspace outside an existing workspace", ws, base); return; } File eclipseDir = workspaceDir; IO.mkdirs(workspaceDir); if (!opts.single()) workspaceDir = new File(workspaceDir, "scm"); IO.mkdirs(workspaceDir); if (!base.isDirectory()) { bnd.error("Could not create directory for the bnd workspace %s", base); } else if (!eclipseDir.isDirectory()) { bnd.error("Could not create directory for the Eclipse workspace %s", eclipseDir); } if (!workspaceDir.isDirectory()) { bnd.error("Could not create the workspace directory %s", workspaceDir); return; } if (!opts.update() && !opts.force() && workspaceDir.list().length > 0) { bnd.error("The workspace directory %s is not empty, specify -u/--update to update or -f/--force to replace", workspaceDir); } InputStream in = getClass().getResourceAsStream("/templates/enroute.zip"); if (in == null) { bnd.error("Cannot find template in this jar %s", "/templates/enroute.zip"); return; } Pattern glob = Pattern.compile("[^/]+|cnf/.*|\\...+/.*"); copy(workspaceDir, in, glob, opts.force()); File readme = new File(workspaceDir, "README.md"); if (readme.isFile()) IO.copy(readme, bnd.out); bnd.out.printf( "%nWorkspace %s created.%n%n" // + " Start Eclipse:%n" // + " 1) Select the Eclipse workspace %s%n" // + " 2) Package Explorer context menu: Import/General/Existing Projects from %s%n" + "%n" + "", // workspaceDir.getName(), eclipseDir, workspaceDir); } private void copy(File workspaceDir, InputStream in, Pattern glob, boolean overwrite) throws Exception { try (Jar jar = new Jar("dot", in)) { for (Entry<String,Resource> e : jar.getResources().entrySet()) { String path = e.getKey(); logger.debug("path {}", path); if (glob != null && !glob.matcher(path).matches()) continue; Resource r = e.getValue(); File dest = Processor.getFile(workspaceDir, path); if (overwrite || !dest.isFile() || dest.lastModified() < r.lastModified() || r.lastModified() <= 0) { logger.debug("copy {} to {}", path, dest); File dp = dest.getParentFile(); IO.mkdirs(dp); IO.copy(r.openInputStream(), dest); } } } } }