1+ /*
2+ * Copyright 2013-2024, Seqera Labs
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package nextflow.packages
18+
19+ import java.nio.file.Path
20+ import java.util.concurrent.ConcurrentHashMap
21+
22+ import groovy.transform.CompileStatic
23+ import groovy.util.logging.Slf4j
24+ import nextflow.Session
25+ import nextflow.plugin.PluginsFacade
26+
27+ /**
28+ * Manages package providers and coordinates package environment creation
29+ *
30+ * @author Edmund Miller <[email protected] > 31+ */
32+ @Slf4j
33+ @CompileStatic
34+ class PackageManager {
35+
36+ private final Map<String , PackageProvider > providers = new ConcurrentHashMap<> ()
37+ private final Session session
38+
39+ PackageManager (Session session ) {
40+ this . session = session
41+ initializeProviders()
42+ }
43+
44+ /**
45+ * Initialize available package providers from plugins
46+ */
47+ private void initializeProviders () {
48+ // Load package providers from plugins
49+ def extensions = PluginsFacade . getExtensions(PackageProviderExtension )
50+ for (PackageProviderExtension extension : extensions) {
51+ def provider = extension. createProvider(session)
52+ if (provider && provider. isAvailable()) {
53+ providers. put(provider. getName(), provider)
54+ log. debug " Registered package provider: ${ provider.getName()} "
55+ }
56+ }
57+ }
58+
59+ /**
60+ * Get a package provider by name
61+ *
62+ * @param name Provider name (e.g., "conda", "pixi")
63+ * @return The package provider or null if not found
64+ */
65+ PackageProvider getProvider (String name ) {
66+ return providers. get(name)
67+ }
68+
69+ /**
70+ * Get all available package providers
71+ *
72+ * @return Map of provider name to provider instance
73+ */
74+ Map<String , PackageProvider > getProviders () {
75+ return Collections . unmodifiableMap(providers)
76+ }
77+
78+ /**
79+ * Create a package environment using the appropriate provider
80+ *
81+ * @param spec The package specification
82+ * @return The path to the created environment
83+ */
84+ Path createEnvironment (PackageSpec spec ) {
85+ if (! spec. isValid()) {
86+ throw new IllegalArgumentException (" Invalid package specification: ${ spec} " )
87+ }
88+
89+ def provider = getProvider(spec. provider)
90+ if (! provider) {
91+ throw new IllegalArgumentException (" Package provider not found: ${ spec.provider} " )
92+ }
93+
94+ if (! provider. supportsSpec(spec)) {
95+ throw new IllegalArgumentException (" Package specification not supported by provider ${ spec.provider} : ${ spec} " )
96+ }
97+
98+ return provider. createEnvironment(spec)
99+ }
100+
101+ /**
102+ * Get the activation script for an environment
103+ *
104+ * @param spec The package specification
105+ * @param envPath Path to the environment
106+ * @return Shell script snippet to activate the environment
107+ */
108+ String getActivationScript (PackageSpec spec , Path envPath ) {
109+ def provider = getProvider(spec. provider)
110+ if (! provider) {
111+ throw new IllegalArgumentException (" Package provider not found: ${ spec.provider} " )
112+ }
113+
114+ return provider. getActivationScript(envPath)
115+ }
116+
117+ /**
118+ * Parse a package specification from process configuration
119+ *
120+ * @param packageDef Package definition (string or map)
121+ * @param provider Default provider if not specified
122+ * @return Parsed package specification
123+ */
124+ static PackageSpec parseSpec (Object packageDef , String provider = null ) {
125+ if (packageDef instanceof String ) {
126+ return new PackageSpec (provider, [packageDef])
127+ } else if (packageDef instanceof List ) {
128+ return new PackageSpec (provider, packageDef as List<String > )
129+ } else if (packageDef instanceof Map ) {
130+ def map = packageDef as Map
131+ def spec = new PackageSpec ()
132+
133+ if (map. containsKey(' provider' )) {
134+ spec. provider = map. provider as String
135+ } else if (provider) {
136+ spec. provider = provider
137+ }
138+
139+ if (map. containsKey(' packages' )) {
140+ def packages = map. packages
141+ if (packages instanceof String ) {
142+ spec. entries = [packages]
143+ } else if (packages instanceof List ) {
144+ spec. entries = packages as List<String >
145+ }
146+ }
147+
148+ if (map. containsKey(' environment' )) {
149+ spec. environment = map. environment as String
150+ }
151+
152+ if (map. containsKey(' channels' )) {
153+ def channels = map. channels
154+ if (channels instanceof List ) {
155+ spec. channels = channels as List<String >
156+ } else if (channels instanceof String ) {
157+ spec. channels = [channels]
158+ }
159+ }
160+
161+ if (map. containsKey(' options' )) {
162+ spec. options = map. options as Map<String , Object >
163+ }
164+
165+ return spec
166+ }
167+
168+ throw new IllegalArgumentException (" Invalid package definition: ${ packageDef} " )
169+ }
170+
171+ /**
172+ * Check if the package manager feature is enabled
173+ *
174+ * @param session The current session
175+ * @return True if the feature is enabled
176+ */
177+ static boolean isEnabled (Session session ) {
178+ return session. config. navigate(' nextflow.preview.package' , false ) as Boolean
179+ }
180+ }
0 commit comments