1 #include <string.h>
2
3 #include "jimautoconf.h"
4 #include <jim-subcmd.h>
5
6 #ifdef HAVE_UNISTD_H
7 #include <unistd.h>
8 #else
9 #define R_OK 4
10 #endif
11
12 /* All packages have a fixed, dummy version */
13 static const char *package_version_1 = "1.0";
14
15 /* -----------------------------------------------------------------------------
16 * Packages handling
17 * ---------------------------------------------------------------------------*/
18
Jim_PackageProvide(Jim_Interp * interp,const char * name,const char * ver,int flags)19 int Jim_PackageProvide(Jim_Interp *interp, const char *name, const char *ver, int flags)
20 {
21 /* If the package was already provided returns an error. */
22 Jim_HashEntry *he = Jim_FindHashEntry(&interp->packages, name);
23
24 /* An empty result means the automatic entry. This can be replaced */
25 if (he && *(const char *)he->u.val) {
26 if (flags & JIM_ERRMSG) {
27 Jim_SetResultFormatted(interp, "package \"%s\" was already provided", name);
28 }
29 return JIM_ERR;
30 }
31 Jim_ReplaceHashEntry(&interp->packages, name, (char *)ver);
32 return JIM_OK;
33 }
34
35 /**
36 * Searches along a of paths for the given package.
37 *
38 * Returns the allocated path to the package file if found,
39 * or NULL if not found.
40 */
JimFindPackage(Jim_Interp * interp,Jim_Obj * prefixListObj,const char * pkgName)41 static char *JimFindPackage(Jim_Interp *interp, Jim_Obj *prefixListObj, const char *pkgName)
42 {
43 int i;
44 char *buf = Jim_Alloc(JIM_PATH_LEN);
45 int prefixc = Jim_ListLength(interp, prefixListObj);
46
47 for (i = 0; i < prefixc; i++) {
48 Jim_Obj *prefixObjPtr = Jim_ListGetIndex(interp, prefixListObj, i);
49 const char *prefix = Jim_String(prefixObjPtr);
50
51 /* Loadable modules are tried first */
52 #ifdef jim_ext_load
53 snprintf(buf, JIM_PATH_LEN, "%s/%s.so", prefix, pkgName);
54 if (access(buf, R_OK) == 0) {
55 return buf;
56 }
57 #endif
58 if (strcmp(prefix, ".") == 0) {
59 snprintf(buf, JIM_PATH_LEN, "%s.tcl", pkgName);
60 }
61 else {
62 snprintf(buf, JIM_PATH_LEN, "%s/%s.tcl", prefix, pkgName);
63 }
64
65 if (access(buf, R_OK) == 0) {
66 return buf;
67 }
68 }
69 Jim_Free(buf);
70 return NULL;
71 }
72
73 /* Search for a suitable package under every dir specified by JIM_LIBPATH,
74 * and load it if possible. If a suitable package was loaded with success
75 * JIM_OK is returned, otherwise JIM_ERR is returned. */
JimLoadPackage(Jim_Interp * interp,const char * name,int flags)76 static int JimLoadPackage(Jim_Interp *interp, const char *name, int flags)
77 {
78 int retCode = JIM_ERR;
79 Jim_Obj *libPathObjPtr = Jim_GetGlobalVariableStr(interp, JIM_LIBPATH, JIM_NONE);
80 if (libPathObjPtr) {
81 char *path;
82
83 /* Scan every directory for the the first match */
84 path = JimFindPackage(interp, libPathObjPtr, name);
85 if (path) {
86 const char *p;
87
88 /* Note: Even if the file fails to load, we consider the package loaded.
89 * This prevents issues with recursion.
90 * Use a dummy version of "" to signify this case.
91 */
92 Jim_PackageProvide(interp, name, "", 0);
93
94 /* Try to load/source it */
95 p = strrchr(path, '.');
96
97 if (p && strcmp(p, ".tcl") == 0) {
98 Jim_IncrRefCount(libPathObjPtr);
99 retCode = Jim_EvalFileGlobal(interp, path);
100 Jim_DecrRefCount(interp, libPathObjPtr);
101 }
102 #ifdef jim_ext_load
103 else {
104 retCode = Jim_LoadLibrary(interp, path);
105 }
106 #endif
107 if (retCode != JIM_OK) {
108 /* Upon failure, remove the dummy entry */
109 Jim_DeleteHashEntry(&interp->packages, name);
110 }
111 Jim_Free(path);
112 }
113
114 return retCode;
115 }
116 return JIM_ERR;
117 }
118
Jim_PackageRequire(Jim_Interp * interp,const char * name,int flags)119 int Jim_PackageRequire(Jim_Interp *interp, const char *name, int flags)
120 {
121 Jim_HashEntry *he;
122
123 /* Start with an empty error string */
124 Jim_SetEmptyResult(interp);
125
126 he = Jim_FindHashEntry(&interp->packages, name);
127 if (he == NULL) {
128 /* Try to load the package. */
129 int retcode = JimLoadPackage(interp, name, flags);
130 if (retcode != JIM_OK) {
131 if (flags & JIM_ERRMSG) {
132 int len = Jim_Length(Jim_GetResult(interp));
133 Jim_SetResultFormatted(interp, "%#s%sCan't load package %s",
134 Jim_GetResult(interp), len ? "\n" : "", name);
135 }
136 return retcode;
137 }
138
139 /* In case the package did not 'package provide' */
140 Jim_PackageProvide(interp, name, package_version_1, 0);
141
142 /* Now it must exist */
143 he = Jim_FindHashEntry(&interp->packages, name);
144 }
145
146 Jim_SetResultString(interp, he->u.val, -1);
147 return JIM_OK;
148 }
149
150 /*
151 *----------------------------------------------------------------------
152 *
153 * package provide name ?version?
154 *
155 * This procedure is invoked to declare that
156 * a particular package is now present in an interpreter.
157 * The package must not already be provided in the interpreter.
158 *
159 * Results:
160 * Returns JIM_OK and sets results as "1.0" (the given version is ignored)
161 *
162 *----------------------------------------------------------------------
163 */
package_cmd_provide(Jim_Interp * interp,int argc,Jim_Obj * const * argv)164 static int package_cmd_provide(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
165 {
166 return Jim_PackageProvide(interp, Jim_String(argv[0]), package_version_1, JIM_ERRMSG);
167 }
168
169 /*
170 *----------------------------------------------------------------------
171 *
172 * package require name ?version?
173 *
174 * This procedure is load a given package.
175 * Note that the version is ignored.
176 *
177 * Results:
178 * Returns JIM_OK and sets the package version.
179 *
180 *----------------------------------------------------------------------
181 */
package_cmd_require(Jim_Interp * interp,int argc,Jim_Obj * const * argv)182 static int package_cmd_require(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
183 {
184 /* package require failing is important enough to add to the stack */
185 interp->addStackTrace++;
186
187 return Jim_PackageRequire(interp, Jim_String(argv[0]), JIM_ERRMSG);
188 }
189
190 /*
191 *----------------------------------------------------------------------
192 *
193 * package list
194 *
195 * Returns a list of known packages
196 *
197 * Results:
198 * Returns JIM_OK and sets a list of known packages.
199 *
200 *----------------------------------------------------------------------
201 */
package_cmd_list(Jim_Interp * interp,int argc,Jim_Obj * const * argv)202 static int package_cmd_list(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
203 {
204 Jim_HashTableIterator *htiter;
205 Jim_HashEntry *he;
206 Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
207
208 htiter = Jim_GetHashTableIterator(&interp->packages);
209 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
210 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, he->key, -1));
211 }
212 Jim_FreeHashTableIterator(htiter);
213
214 Jim_SetResult(interp, listObjPtr);
215
216 return JIM_OK;
217 }
218
219 static const jim_subcmd_type package_command_table[] = {
220 {
221 "provide",
222 "name ?version?",
223 package_cmd_provide,
224 1,
225 2,
226 /* Description: Indicates that the current script provides the given package */
227 },
228 {
229 "require",
230 "name ?version?",
231 package_cmd_require,
232 1,
233 2,
234 /* Description: Loads the given package by looking in standard places */
235 },
236 {
237 "list",
238 NULL,
239 package_cmd_list,
240 0,
241 0,
242 /* Description: Lists all known packages */
243 },
244 {
245 NULL
246 }
247 };
248
Jim_packageInit(Jim_Interp * interp)249 int Jim_packageInit(Jim_Interp *interp)
250 {
251 Jim_CreateCommand(interp, "package", Jim_SubCmdProc, (void *)package_command_table, NULL);
252 return JIM_OK;
253 }
254